package com.hsurosy.infrastructure.persistent.repository;

import com.hsurosy.domain.strategy.model.entity.StrategyAwardEntity;
import com.hsurosy.domain.strategy.model.entity.StrategyEntity;
import com.hsurosy.domain.strategy.model.entity.StrategyRuleEntity;
import com.hsurosy.domain.strategy.model.valobj.*;
import com.hsurosy.domain.strategy.repository.IStrategyRepository;
import com.hsurosy.infrastructure.persistent.dao.*;
import com.hsurosy.infrastructure.persistent.po.*;
import com.hsurosy.infrastructure.persistent.redis.IRedisService;
import com.hsurosy.types.common.Constants;
import com.hsurosy.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.hsurosy.types.enums.ResponseCode.UN_ASSEMBLED_STRATEGY_ARMORY;

/**
 * @Author Hsu琛君珩
 * @Date 2024-10-15 17:06
 * @Description 抽奖策略仓储实现
 * @Version: v1.0.0
 */
@Slf4j
@Repository
public class StrategyRepository implements IStrategyRepository {

    @Resource
    private IStrategyRuleDao strategyRuleDao;
    @Resource
    private IStrategyDao strategyDao;
    @Resource
    private IStrategyAwardDao strategyAwardDao;
    @Resource
    private IRuleTreeDao ruleTreeDao;
    @Resource
    private IRuleTreeNodeDao ruleTreeNodeDao;
    @Resource
    private IRuleTreeNodeLineDao ruleTreeNodeLineDao;

    @Resource
    private IRedisService redisService;

    /**
     * 查询抽奖策略的奖品列表，优先从缓存获取，如果缓存不存在则从数据库获取并写入缓存
     *
     * @param strategyId 抽奖策略ID
     * @return 该策略对应的奖品列表
     */
    @Override
    public List<StrategyAwardEntity> queryStrategyAwardList(Long strategyId) {
        // 1. 生成缓存的key，用于标识特定策略的奖品信息
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_LIST_KEY + strategyId;
        // 2. 尝试从Redis缓存中获取奖品列表
        List<StrategyAwardEntity> strategyAwardEntities = redisService.getValue(cacheKey);
        // 3. 如果缓存命中且不为空，直接返回缓存中的奖品列表
        if (null != strategyAwardEntities && !strategyAwardEntities.isEmpty()) {
            return strategyAwardEntities;
        }
        // 4. 如果缓存未命中，则从数据库中读取奖品列表
        List<StrategyAward> strategyAwards = strategyAwardDao.queryStrategyAwardListByStrategyId(strategyId);
        // 5. 将数据库中获取的奖品列表转化为领域对象（StrategyAwardEntity）
        strategyAwardEntities = new ArrayList<>(strategyAwards.size());
        for (StrategyAward strategyAward : strategyAwards) {
            StrategyAwardEntity strategyAwardEntity = StrategyAwardEntity.builder()
                    .strategyId(strategyAward.getStrategyId())
                    .awardId(strategyAward.getAwardId())
                    .awardTitle(strategyAward.getAwardTitle())
                    .awardSubtitle(strategyAward.getAwardSubtitle())
                    .awardCount(strategyAward.getAwardCount())
                    .awardCountSurplus(strategyAward.getAwardCountSurplus())
                    .awardRate(strategyAward.getAwardRate())
                    .sort(strategyAward.getSort())
                    .build();
            strategyAwardEntities.add(strategyAwardEntity);  // 添加到列表中
        }
        // 6. 将奖品列表存入Redis缓存，避免重复从数据库读取
        redisService.setValue(cacheKey, strategyAwardEntities);
        // 7. 返回奖品列表
        return strategyAwardEntities;
    }

    /**
     * 存储抽奖策略的概率查找表
     *
     * @param key 抽奖策略ID
     * @param rateRange 抽奖概率范围，用于生成随机数
     * @param strategyAwardSearchRateTable 该策略的奖品概率查找表
     */
    @Override
    public void storeStrategyAwardSearchRateTable(String key, Integer rateRange, Map<Integer, Integer> strategyAwardSearchRateTable) {
        // 1. 将抽奖概率范围值存入Redis，供后续计算使用，如 10000，用于生成 0~9999 以内的随机数
        // 比如 big_market_strategy_rate_range_key_100001 10000(这个是范围)
        redisService.setValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key, rateRange);
        // 2. 将奖品概率查找表存入Redis中，缓存查找表以提高性能
        // 比如 big_market_strategy_rate_table_key_100001 表(和 strategyAwardSearchRateTable 一样的，也就是前面传入的 shuffleStrategyAwardSearchRateTable)
        Map<Integer, Integer> cacheRateTable = redisService.getMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key);
        // 3. 将新生成的概率查找表（strategyAwardSearchRateTable）的内容存入缓存表（cacheRateTable）中
        cacheRateTable.putAll(strategyAwardSearchRateTable);
    }

    /**
     * 获取某个策略的抽奖概率范围
     *
     * @param strategyId 抽奖策略ID
     * @return 抽奖概率范围，用于随机数计算
     */
    @Override
    public int getRateRange(Long strategyId) {
        // 从Redis中获取指定策略的概率范围值
        return getRateRange(String.valueOf(strategyId));
    }

    @Override
    public int getRateRange(String key) {
        String cacheKey = Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key;
        if (!redisService.isExists(cacheKey)) {
            throw new AppException(UN_ASSEMBLED_STRATEGY_ARMORY.getCode(), cacheKey + Constants.COLON + UN_ASSEMBLED_STRATEGY_ARMORY.getInfo());
        }
        // 从Redis中获取指定策略的概率范围值
        return redisService.getValue(Constants.RedisKey.STRATEGY_RATE_RANGE_KEY + key);
    }

    /**
     * 根据概率键获取对应的奖品ID
     *
     * @param key 抽奖策略ID
     * @param rateKey 概率键，用于查找具体奖品
     * @return 对应的奖品ID
     */
    @Override
    public Integer getStrategyAwardAssemble(String key, Integer rateKey) {
        // 从Redis中获取指定策略的概率查找表，依据rateKey获取对应的奖品ID
        return redisService.getFromMap(Constants.RedisKey.STRATEGY_RATE_TABLE_KEY + key, rateKey);
        /*
            比如
            随机数范围        对应奖品ID
            0 - 999         → 奖品ID 101 (对应奖品A，概率10%)
            1000 - 1499     → 奖品ID 102 (对应奖品B，概率5%)
            1500 - 1599     → 奖品ID 103 (对应奖品C，概率1%)
            ...（其他区间可能为空奖或特殊规则）...
        */
    }

    /**
     * 根据策略ID查询抽奖策略实体信息
     * 该方法首先尝试从缓存中获取策略信息，如果缓存中没有，则从数据库中查询，并将查询结果存入缓存中。
     *
     * @param strategyId 抽奖策略ID
     * @return 返回对应的抽奖策略实体信息，包含策略ID、策略描述和规则模型等。
     */
    @Override
    public StrategyEntity queryStrategyEntityByStrategyId(Long strategyId) {
        // 1. 优先从缓存中获取该策略ID对应的抽奖策略实体信息
        String cacheKey = Constants.RedisKey.STRATEGY_KEY + strategyId;
        StrategyEntity strategyEntity = redisService.getValue(cacheKey);
        // 2. 如果缓存中存在该策略的信息，直接返回缓存中的策略实体
        if (null != strategyEntity) {
            return strategyEntity;
        }
        // 3. 如果缓存中不存在，则从数据库中查询该策略的详细信息
        Strategy strategy = strategyDao.queryStrategyByStrategyId(strategyId);
        // 4. 构建策略实体对象，填充查询到的策略信息
        strategyEntity = StrategyEntity.builder()
                .strategyId(strategy.getStrategyId())  // 设置策略ID
                .strategyDesc(strategy.getStrategyDesc())  // 设置策略描述
                .ruleModels(strategy.getRuleModels())  // 设置该策略的规则模型
                .build();
        // 5. 将查询到的策略信息存入Redis缓存中，便于下次快速访问
        redisService.setValue(cacheKey, strategyEntity);
        // 6. 返回构建好的策略实体对象
        return strategyEntity;
    }

    /**
     * 根据策略ID和规则模型名称查询指定的抽奖策略规则信息
     * 该方法根据策略ID和规则模型名称查询对应的规则，并返回一个规则实体对象。
     *
     * @param strategyId 抽奖策略ID
     * @param ruleModel 抽奖规则模型名称，用于指定需要查询的规则类型
     * @return 返回查询到的规则实体对象，包含规则的详细信息。
     */
    @Override
    public StrategyRuleEntity queryStrategyRule(Long strategyId, String ruleModel) {
        // 1. 构建查询条件对象，包含策略ID和规则模型名称
        StrategyRule strategyRuleReq = new StrategyRule();
        strategyRuleReq.setStrategyId(strategyId);  // 设置策略ID
        strategyRuleReq.setRuleModel(ruleModel);  // 设置规则模型名称
        // 2. 通过DAO查询数据库，获取该策略ID下指定规则模型的规则信息
        StrategyRule strategyRuleRes = strategyRuleDao.queryStrategyRule(strategyRuleReq);
        // 3. 构建规则实体对象，将查询到的规则信息填充到规则实体中
        return StrategyRuleEntity.builder()
                .strategyId(strategyRuleRes.getStrategyId())  // 设置策略ID
                .awardId(strategyRuleRes.getAwardId())  // 设置奖品ID
                .ruleType(strategyRuleRes.getRuleType())  // 设置规则类型（例如：幸运奖、解锁规则等）
                .ruleModel(strategyRuleRes.getRuleModel())  // 设置规则模型名称
                .ruleValue(strategyRuleRes.getRuleValue())  // 设置规则的数值
                .ruleDesc(strategyRuleRes.getRuleDesc())  // 设置规则描述，说明规则的作用和限制
                .build();  // 构建并返回规则实体对象
    }

    @Override
    public String queryStrategyRuleValue(Long strategyId, String ruleModel) {
        return queryStrategyRuleValue(strategyId, null, ruleModel);
    }

    /**
     * 根据策略ID、奖品ID和规则模型查询对应的规则值
     *
     * @param strategyId 抽奖策略ID
     * @param awardId 奖品ID
     * @param ruleModel 规则模型名称
     * @return 返回对应的规则值
     */
    @Override
    public String queryStrategyRuleValue(Long strategyId, Integer awardId, String ruleModel) {
        // 创建 StrategyRule 对象，并设置查询条件
        StrategyRule strategyRule = new StrategyRule();
        strategyRule.setStrategyId(strategyId);   // 设置策略ID
        strategyRule.setAwardId(awardId);         // 设置奖品ID
        strategyRule.setRuleModel(ruleModel);     // 设置规则模型
        // 调用 DAO 层方法查询并返回规则值
        return strategyRuleDao.queryStrategyRuleValue(strategyRule);
    }


    /**
     * 根据策略ID和奖品ID查询与奖品相关的规则模型
     *
     * @param strategyId 抽奖策略ID
     * @param awardId 抽中的奖品ID
     * @return 包含规则模型的值对象 `StrategyAwardRuleModelVO`
     */
    @Override
    public StrategyAwardRuleModelVO queryStrategyAwardRuleModelVO(Long strategyId, Integer awardId) {
        // 构建 StrategyAward 实体对象，用于传递查询条件（包含策略ID和奖品ID）
        StrategyAward strategyAward = new StrategyAward();
        strategyAward.setStrategyId(strategyId);  // 设置抽奖策略ID
        strategyAward.setAwardId(awardId);        // 设置奖品ID

        // 通过 DAO 层查询与奖品ID相关的规则模型（例如解锁规则、幸运奖规则等）
        // 该方法会返回与该奖品相关的规则模型的字符串形式，通常是多个规则模型的组合
        String ruleModels = strategyAwardDao.queryStrategyAwardRuleModels(strategyAward);

        // 构建并返回 StrategyAwardRuleModelVO 值对象，包含规则模型列表
        // 这个值对象用于后续的规则过滤和校验
        return StrategyAwardRuleModelVO.builder()
                .ruleModels(ruleModels)  // 设置查询到的规则模型字符串
                .build();
    }

    /**
     * 根据规则树ID查询规则树的详细信息，包括规则树节点及其连线。
     * 优先从Redis缓存中获取，缓存中没有则从数据库查询，并将查询结果缓存。
     *
     * @param treeId 规则树ID
     * @return 包含规则树的节点和连线信息的 `RuleTreeVO` 对象
     */
    @Override
    public RuleTreeVO queryRuleTreeVOByTreeId(String treeId) {
        // 1. 优先从缓存中取规则树的值对象
        String cacheKey = Constants.RedisKey.RULE_TREE_VO_KEY + treeId;  // 构建Redis缓存key
        RuleTreeVO ruleTreeVOCache = redisService.getValue(cacheKey);  // 从Redis缓存中获取规则树对象

        // 如果缓存命中，直接返回缓存中的规则树
        if (null != ruleTreeVOCache) {
            return ruleTreeVOCache;
        }

        // 2. 缓存未命中，从数据库获取规则树的详细信息
        RuleTree ruleTree = ruleTreeDao.queryRuleTreeByTreeId(treeId);  // 查询规则树的基础信息
        List<RuleTreeNode> ruleTreeNodes = ruleTreeNodeDao.queryRuleTreeNodeListByTreeId(treeId);  // 查询规则树中的所有节点
        List<RuleTreeNodeLine> ruleTreeNodeLines = ruleTreeNodeLineDao.queryRuleTreeNodeLineListByTreeId(treeId);  // 查询规则树节点的连线

        // 3. 转换规则树节点连线数据，构建Map结构
        Map<String, List<RuleTreeNodeLineVO>> ruleTreeNodeLineMap = new HashMap<>();
        for (RuleTreeNodeLine ruleTreeNodeLine : ruleTreeNodeLines) {
            // 将 `RuleTreeNodeLine` 转换为 `RuleTreeNodeLineVO` 对象，包含连线的详细信息
            RuleTreeNodeLineVO ruleTreeNodeLineVO = RuleTreeNodeLineVO.builder()
                    .treeId(ruleTreeNodeLine.getTreeId())  // 设置连线所属的规则树ID
                    .ruleNodeFrom(ruleTreeNodeLine.getRuleNodeFrom())  // 设置连线的起始节点
                    .ruleNodeTo(ruleTreeNodeLine.getRuleNodeTo())  // 设置连线的终止节点
                    .ruleLimitType(RuleLimitTypeVO.valueOf(ruleTreeNodeLine.getRuleLimitType()))  // 设置连线的限定类型（如等于、大于、小于等）
                    .ruleLimitValue(RuleLogicCheckTypeVO.valueOf(ruleTreeNodeLine.getRuleLimitValue()))  // 设置连线的限定值（如是否允许、接管等）
                    .build();

            // 将连线信息添加到 `ruleTreeNodeLineMap`，以 `ruleNodeFrom` 为key，确保每个节点的所有出连线都被记录
            List<RuleTreeNodeLineVO> ruleTreeNodeLineVOList = ruleTreeNodeLineMap.computeIfAbsent(ruleTreeNodeLine.getRuleNodeFrom(), k -> new ArrayList<>());
            ruleTreeNodeLineVOList.add(ruleTreeNodeLineVO);
        }

        // 4. 转换规则树节点数据，构建Map结构
        Map<String, RuleTreeNodeVO> treeNodeMap = new HashMap<>();
        for (RuleTreeNode ruleTreeNode : ruleTreeNodes) {
            // 将 `RuleTreeNode` 转换为 `RuleTreeNodeVO` 对象，包含节点的详细信息
            RuleTreeNodeVO ruleTreeNodeVO = RuleTreeNodeVO.builder()
                    .treeId(ruleTreeNode.getTreeId())  // 设置节点所属的规则树ID
                    .ruleKey(ruleTreeNode.getRuleKey())  // 设置规则Key（标识每个节点）
                    .ruleDesc(ruleTreeNode.getRuleDesc())  // 设置节点描述
                    .ruleValue(ruleTreeNode.getRuleValue())  // 设置节点值（用于规则计算）
                    .treeNodeLineVOList(ruleTreeNodeLineMap.get(ruleTreeNode.getRuleKey()))  // 设置从该节点出发的所有连线信息
                    .build();

            // 将节点信息存入 `treeNodeMap`，以 `ruleKey` 为key
            treeNodeMap.put(ruleTreeNode.getRuleKey(), ruleTreeNodeVO);
        }

        // 5. 构建完整的规则树 `RuleTreeVO`
        RuleTreeVO ruleTreeVODB = RuleTreeVO.builder()
                .treeId(ruleTree.getTreeId())  // 设置规则树ID
                .treeName(ruleTree.getTreeName())  // 设置规则树名称
                .treeDesc(ruleTree.getTreeDesc())  // 设置规则树描述
                .treeRootRuleNode(ruleTree.getTreeRootRuleKey())  // 设置规则树的根节点
                .treeNodeMap(treeNodeMap)  // 设置规则树中的节点Map
                .build();

        // 6. 将规则树对象存入Redis缓存，以提高后续查询效率
        redisService.setValue(cacheKey, ruleTreeVODB);

        // 7. 返回构建好的规则树 `RuleTreeVO`
        return ruleTreeVODB;
    }

    /**
     * 缓存奖品库存
     *
     * @param cacheKey key
     * @param awardCount 库存值
     */
    @Override
    public void cacheStrategyAwardCount(String cacheKey, Integer awardCount) {
        if (null != redisService.getValue(cacheKey)) return;
        redisService.setAtomicLong(cacheKey, awardCount);
    }

    /**
     * 扣减奖品库存
     *
     * @param cacheKey Redis中存储库存信息的key
     * @return 扣减库存是否成功。返回false表示库存不足或库存锁定失败。
     */
    @Override
    public Boolean subtractionAwardStock(String cacheKey) {
        // 1. 从Redis中扣减库存，surplus表示剩余库存
        long surplus = redisService.decr(cacheKey);

        // 2. 如果扣减后库存小于0，将库存设置为0，并返回false，表示库存不足
        if (surplus < 0) {
            redisService.setValue(cacheKey, 0);
            return false;
        }

        // 3. 如果库存足够，尝试为当前库存位置加锁
        String lockKey = cacheKey + Constants.UNDERLINE + surplus;
        Boolean lock = redisService.setNx(lockKey);

        // 4. 如果加锁失败，记录日志并返回false
        if (!lock) {
            log.info("策略奖品库存加锁失败 {}", lockKey);
        }

        // 5. 返回加锁结果，true表示扣减库存成功并锁定库存位置
        return lock;
    }

    /**
     * 将奖品库存的扣减任务发送到延迟队列，延迟处理库存更新
     *
     * @param strategyAwardStockKeyVO 包含策略ID和奖品ID的库存信息对象
     */
    @Override
    public void awardStockConsumeSendQueue(StrategyAwardStockKeyVO strategyAwardStockKeyVO) {
        // 1. 获取用于存储库存扣减任务的阻塞队列
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUERY_KEY;
        RBlockingQueue<StrategyAwardStockKeyVO> blockingQueue = redisService.getBlockingQueue(cacheKey);

        // 2. 将阻塞队列转换为延迟队列，延迟处理任务
        RDelayedQueue<StrategyAwardStockKeyVO> delayedQueue = redisService.getDelayedQueue(blockingQueue);

        // 3. 将库存扣减任务推送到延迟队列，延迟3秒后处理任务
        delayedQueue.offer(strategyAwardStockKeyVO, 3, TimeUnit.SECONDS);
    }

    /**
     * 从阻塞队列中取出库存扣减任务
     *
     * @return 从队列中取出的库存信息对象，包含策略ID和奖品ID
     */
    @Override
    public StrategyAwardStockKeyVO takeQueueValue() {
        // 1. 获取用于存储库存扣减任务的阻塞队列
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_QUERY_KEY;
        RBlockingQueue<StrategyAwardStockKeyVO> destinationQueue = redisService.getBlockingQueue(cacheKey);

        // 2. 从阻塞队列中取出一个库存扣减任务并返回
        return destinationQueue.poll();
    }

    /**
     * 更新奖品库存到数据库中
     *
     * @param strategyId 抽奖策略ID
     * @param awardId 奖品ID
     */
    @Override
    public void updateStrategyAwardStock(Long strategyId, Integer awardId) {
        // 1. 创建StrategyAward对象，设置策略ID和奖品ID
        StrategyAward strategyAward = new StrategyAward();
        strategyAward.setStrategyId(strategyId);
        strategyAward.setAwardId(awardId);

        // 2. 调用DAO层方法，更新数据库中的奖品库存
        strategyAwardDao.updateStrategyAwardStock(strategyAward);
    }

    /**
     * 根据策略ID和奖品ID的唯一组合值，查询对应的奖品信息
     *
     * @param strategyId 策略ID
     * @param awardId 奖品ID
     * @return 返回对应的奖品信息实体
     */
    @Override
    public StrategyAwardEntity queryStrategyAwardEntity(Long strategyId, Integer awardId) {
        // 1. 生成缓存的key，用于标识特定策略的奖品信息
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_KEY + strategyId + Constants.UNDERLINE + awardId;

        // 2. 优先从Redis缓存中获取该奖品信息，减少数据库查询的次数
        StrategyAwardEntity strategyAwardEntity = redisService.getValue(cacheKey);
        if (null != strategyAwardEntity) {
            // 如果缓存中有对应的数据，直接返回缓存结果
            return strategyAwardEntity;
        }

        // 3. 缓存未命中，则从数据库中查询该策略和奖品ID对应的奖品信息
        StrategyAward strategyAwardReq = new StrategyAward();
        strategyAwardReq.setStrategyId(strategyId);  // 设置策略ID
        strategyAwardReq.setAwardId(awardId);        // 设置奖品ID
        StrategyAward strategyAwardRes = strategyAwardDao.queryStrategyAward(strategyAwardReq);

        // 4. 将查询到的数据库结果转换为领域实体对象（StrategyAwardEntity）
        strategyAwardEntity = StrategyAwardEntity.builder()
                .strategyId(strategyAwardRes.getStrategyId())  // 设置策略ID
                .awardId(strategyAwardRes.getAwardId())        // 设置奖品ID
                .awardTitle(strategyAwardRes.getAwardTitle())  // 设置奖品标题
                .awardSubtitle(strategyAwardRes.getAwardSubtitle())  // 设置奖品副标题
                .awardCount(strategyAwardRes.getAwardCount())  // 设置奖品总数
                .awardCountSurplus(strategyAwardRes.getAwardCountSurplus())  // 设置剩余奖品数量
                .awardRate(strategyAwardRes.getAwardRate())    // 设置奖品中奖率
                .sort(strategyAwardRes.getSort())              // 设置奖品排序值
                .build();

        // 5. 将奖品信息写入Redis缓存，以加速下次的查询
        redisService.setValue(cacheKey, strategyAwardEntity);

        // 6. 返回奖品信息实体
        return strategyAwardEntity;
    }

}
